/*
 * @描述: 
 * @创建者: kilomChou
 * @Date: 2021-04-26 19:21:54
 * @最后编辑: kilomChou
 * @LastEditTime: 2021-05-05 20:45:52
 */
import fs from "fs";
import path from "path";
import { ValueType as VT } from "k-coder";

/** Typescript kproto 协议构建 */
export default class TsKprotoBuilder {

    /** 通用结构定义 */
    private comDefs: ICommonDef[] = [];
    /** 协议结构定义 */
    private protoDefs: IProtoDef[] = [];
    /** 当前协议号 */
    private curProtocol: number = 0;
    /** 协议号区间定义 */
    private protocolDefs: { fileName: string, def: number[] }[] = [];
    /** 代码 */
    private codeStr: string = "";

    constructor(
        /** 协议目录 */
        readonly inputDir: string,
        /** 输出目录 */
        readonly outputDir: string,
        /** 输出类名 */
        readonly clsName = "Proto"
    ) {

    }

    /** 构建协议  */
    build() {
        this.loadAllProto();
        this.sortProto();
        this.buildCodeStr();
        fs.writeFileSync(path.resolve(this.outputDir, `${this.clsName}.ts`), this.codeStr, { encoding: "utf-8", flag: "w+" })
    }

    /** 读取所有协议 */
    private loadAllProto() {
        //遍历协议目录及其子目录读取协议
        (function eachDir(dir: string, fn: (filePath: string, fileName: string) => void) {
            fs.readdirSync(dir).forEach(fileName => {
                let filePath = path.resolve(dir, fileName);
                if (fs.statSync(path.resolve(dir, fileName)).isDirectory()) {
                    //递归遍历子目录
                    eachDir(filePath, fn);
                } else {
                    fn(filePath, fileName);
                }
            })
        })(this.inputDir, (filePath, fileName) => {
            let match = fileName.match(/(.*)\.kproto$/);
            if (match) {
                //检查命名是否规范
                console.log(filePath);
                assert(!match[1].match(/[^_0-9a-zA-Z]/), `[${fileName}.kproto]命名不规范`);
                let file = fs.readFileSync(filePath, "utf-8");
                this.loadProto(file, match[1]);
            }
        });
    }

    /** 读取单个协议 */
    private loadProto(file: string, fileName: string) {

        //忽略#号后内容
        file = file.replace(/#.*/g, "");
        let protocolDef: number[];
        let curProtocol: number;
        let def: ICommonDef | IProtoDef;
        let struct: IStructDef;
        let note: string[] = [];


        //遍历每行读取定义
        file.split("\r\n").forEach((ln, lnIdx) => {
            if (!ln) return;
            if (!protocolDef) {
                //找出protocol定义
                let match = ln.match(/^protocol *= *(.*) *;/)
                if (match) {

                    match = assert(match[1].match(/(\/)|(\d+,\d+)/), errMsg("protocol格式不正确"));
                    if (match[1]) {
                        protocolDef = [];
                    } else {
                        let span = match[2].split(",");
                        protocolDef = [parseInt(span[0]), parseInt(span[1])];
                        assert(protocolDef[0], errMsg("protocol定义错误：起始协议号不能为0"));
                        assert(protocolDef[0] <= protocolDef[1], errMsg("protocol定义错误：起始协议号不能大于结束协议号"));
                        this.protocolDefs.forEach(def => {
                            //协议号区间不能有交集
                            assert(!(Math.max(protocolDef[0], def.def[0]) <= Math.min(protocolDef[1], def.def[1])),
                                errMsg(`协议号区间(${match[2]})与[${def.fileName}.kproto]的协议号区间(${def.def.join(",")})冲突`))
                        })
                        curProtocol = protocolDef[0];
                        this.curProtocol = Math.max(this.curProtocol, protocolDef[1] + 1);
                        this.protocolDefs.push({ fileName, def: protocolDef });
                    }
                }
            } else {
                let match = ln.match(/^\s*\/\/(.*)/);
                if (match) {

                    //匹配到注释
                    note.push(match[1]);
                    return;
                }
                if (!def) {
                    //===匹配定义
                    match = ln.match(/^\s*(com|proto) +([_0-9a-zA-Z]+) *({|;)/)
                    if (match) {
                        //匹配到定义
                        let isCom = match[1] == "com";
                        let name = match[2].toUpperCase();
                        let voName = `I${name}`;
                        def = {
                            protocol: undefined,
                            protoName: name,
                            fileName,
                            voName,
                            note,
                            structName: name,
                            fields: [],
                            structs: [],
                            struct: "[]"
                        }
                        if (isCom) {
                            //通用结构定义
                            this.comDefs.push(def)
                        } else {
                            //协议结构定义
                            if (curProtocol) {
                                def.protocol = curProtocol++;
                                assert(def.protocol <= protocolDef[1], errMsg(`协议号${def.protocol}超出定义的结束协议号${protocolDef[1]}，请扩充`));
                            }
                            this.protoDefs.push(def)
                        }
                        if (match[3] == ";") {
                            //无结构纯定义
                            def = null;
                        }
                    } else {
                        checkOther(ln);
                    }
                } else {
                    //===读取定义
                    if (!struct) {
                        //读取com或proto定义
                        match = ln.match(fieldReg);
                        if (match) {
                            //匹配到字段
                            def.fields.push(parseField(match));
                        } else {
                            match = ln.match(/^\s*struct +([_0-9a-zA-Z]+) *{/);
                            if (match) {
                                //匹配到内部结构
                                let name = `${def.structName}_${match[1].toUpperCase()}`;
                                let voName = `I${name}`;
                                def.structs.push(struct = {
                                    note,
                                    structName: name,
                                    voName,
                                    fields: [],
                                    struct: ""
                                })
                            } else if (ln.match(/^\s*\}/)) {
                                //匹配到定义结束
                                def.struct = buildFieldsStruct(def.fields);
                                if (!def.struct) {
                                    //没有结构
                                    def.voName = "undefined";
                                }
                                def = null;
                            } else {
                                checkOther(ln);
                            }
                        }
                    } else {
                        //读取内部结构定义
                        match = ln.match(fieldReg);
                        if (match) {
                            //匹配到内部结构字段
                            struct.fields.push(parseField(match));
                        } else if (ln.match(/^\s*\}/)) {
                            //匹配到内部结构定义结束
                            struct.struct = buildFieldsStruct(struct.fields);
                            struct = null;
                        } else {
                            checkOther(ln);
                        }
                    }
                }
                note = [];
            }
            function errMsg(msg: string) {
                return `[${fileName}.kproto]:${lnIdx + 1} >>> ${ln} >>> ${msg}`
            }
            function checkOther(ln: string) {
                assert(!/\S/.test(ln), errMsg("错误定义"))
            }

            /** 解析字段 */
            function parseField(match: RegExpMatchArray): IFieldDef {
                let name = match[1];
                /** 是否使用通用结构 */
                let isCom = match[2] == "@";
                let type = match[3];
                /** 特殊浮点型参数 */
                let floatParam = match[4] ? parseInt(match[4]) : 0;
                /** 数组维度 */
                let arrVec = match[5] === "" ? 1 : match[5] ? parseInt(match[5]) : 0;

                let stfn = "";
                let tsType: string;
                let struct: any[] = [`"${name.replace("?", "")}"`];
                switch (type) {
                    case "bool":
                        tsType = "boolean"
                        struct.push(arrVec ? VT.boolArr : VT.bool)
                        break
                    case "uint":
                        tsType = "number"
                        struct.push(arrVec ? VT.uintArr : VT.uint)
                        break
                    case "int":
                        tsType = "number"
                        struct.push(arrVec ? VT.intArr : VT.int)
                        break
                    case "float":
                        tsType = "number"
                        struct.push(arrVec ? VT.floatArr : VT.float)
                        break
                    case "floatUint":
                        tsType = "number"
                        struct.push(arrVec ? VT.floatUintArr : VT.floatUint)
                        break
                    case "floatInt":
                        tsType = "number"
                        struct.push(arrVec ? VT.floatIntArr : VT.floatInt)
                        break
                    case "floatStr":
                        tsType = "number"
                        struct.push(arrVec ? VT.floatStrArr : VT.floatStr)
                        break
                    case "str":
                        tsType = "string"
                        struct.push(arrVec ? VT.strArr : VT.str)
                        break
                    case "obj":
                        tsType = "any"
                        struct.push(VT.obj)
                        break
                    case "arr":
                        tsType = "any[]"
                        struct.push(VT.arr)
                        break
                    default:
                        //自定义结构类型
                        if (isCom) {
                            //通用结构
                            tsType = `I${type}`.toUpperCase()
                            stfn = `ST.${type.toUpperCase()}()`
                        } else {
                            //内部结构
                            tsType = `I${def.structName}_${type}`.toUpperCase()
                            stfn = `ST.${def.structName.toUpperCase()}_${type.toUpperCase()}()`
                        }
                        struct.push(arrVec ? VT.structArr : VT.struct);
                        break
                }
                if (arrVec) {
                    //数组处理
                    struct.push(arrVec);
                    while (arrVec-- > 0) {
                        tsType += "[]"
                    }
                }
                //特殊参数处理
                floatParam && struct.push(floatParam);
                stfn && struct.push(stfn);

                return {
                    lnIdx,
                    note,
                    name,
                    type: tsType,
                    struct: `[${struct.join(", ")}]`
                }
            }
        });
        assert(protocolDef, `[${fileName}.kproto] >>> 未找到protocol定义`);

        /** 构建字段结构 */
        function buildFieldsStruct(fields: IFieldDef[]) {
            if (fields.length) {
                let structs = [];
                fields.forEach(field => structs.push(field.struct));
                return `[${structs.join(", ")}]`;
            } else {
                return "";
            }
        }
    }

    /** 根据协议号排序 */
    private sortProto() {
        //先给没有协议号的在当前最大协议号后递增附一个
        this.protoDefs.forEach(def => {
            if (!def.protocol) {
                def.protocol = this.curProtocol++;
            }
        })
        this.protoDefs.sort((a, b) => a.protocol - b.protocol);
    }

    /** 构建协议代码 */
    private buildCodeStr() {
        let code = "//工具生成，请不要手动修改...\n";

        //写入kcoder初始化
        code += "let ObjEncoder: any, ObjDecoder: any\n";
        code += "/** 需要传入kcoder模块来进行初始化 */\n";
        code += `export function init${this.clsName}(kcoder: { ObjEncoder: any, ObjDecoder: any }) {\n`;
        code += "    ObjEncoder = kcoder.ObjEncoder\n";
        code += "    ObjDecoder = kcoder.ObjDecoder\n";
        code += "}\n\n";

        //写入协议号
        code += "/** 协议 */\n";
        code += `export const enum ${this.clsName} {\n`;
        this.protoDefs.forEach(proto => {
            buildNote(proto.note, "    ");
            code += `    ${proto.protoName} = ${proto.protocol},\n`;
        })
        code += "}\n\n";

        //写入通用结构和内部结构
        code += "const ST = {\n";
        let structs: IStructDef[] = [...this.comDefs];
        this.comDefs.forEach(com => structs = structs.concat(com.structs));
        this.protoDefs.forEach(proto => structs = structs.concat(proto.structs));
        structs.forEach(struct => {
            code += `    ${struct.structName}: () => ${struct.struct},\n`;
        })
        code += "}\n\n";

        //写入协议结构
        code += "const STRUCT: any = {\n";
        this.protoDefs.forEach(proto => {
            code += `    ${proto.protocol}: ${proto.struct},\n`;
        })
        code += "}\n\n";

        //写入def
        code += "/** 协议Vo定义 */\n";
        code += "export namespace ProtoVo {\n\n";
        code += "    export type Def = {\n";
        this.protoDefs.forEach(proto => {
            code += `        [${this.clsName}.${proto.protoName}]: ${proto.voName}\n`;
        });
        code += "    }\n\n";

        //写入读写方法
        code += "    /** 二进制数据转换为协议vo */\n";
        code += "    export function fromBytes(bytes: Uint8Array) {\n";
        code += "        let protocol: number, data = ObjDecoder.decodeDeal(bytes, (r: any) => { protocol = r.uint; return STRUCT[protocol] })\n";
        code += "        return { protocol, data }\n";
        code += "    }\n\n";
        code += "    /** 协议vo转换为二进制数据 */\n";
        code += "    export function toBytes<P extends keyof Def>(protocol: P, data: Def[P]) {\n";
        code += "        return ObjEncoder.encodeDeal(data, STRUCT[protocol], (w: any) => w.uint = protocol)\n";
        code += "    }\n\n"

        //写入vo
        let defs = [...this.comDefs, ...this.protoDefs];
        defs.forEach(def => {
            let structs: IStructDef[] = [def, ...def.structs];
            structs.forEach(struct => {
                buildNote(struct.note, "    ");
                code += `    export interface ${struct.voName} {\n`;
                struct.fields.forEach(field => {
                    buildNote(field.note, "        ");
                    code += `        ${field.name}: ${field.type}\n`;
                });
                code += "    }\n\n";
            })
        })

        code += "}"

        //构建完成
        this.codeStr = code;

        /** 构建注释 tab：缩进*/
        function buildNote(note: string[], tab: string) {
            if (!note) return;
            if (note.length == 1) {
                //单行注释
                code += `${tab}/** ${note} */\n`;
            } else {
                //多行注释
                code += `${tab}/**\n`;
                note.forEach(line => code += `${tab} * ${line}\n`);
                code += `${tab} */\n`;
            }
        }
    }
}

/** 字段正则 */
const fieldReg = /^\s*([_0-9a-zA-Z]+\??) *: *(?:(@?)([_0-9a-zA-Z]+)(?:\((\d+)\))*(?:\[(\d*)\])*)+ *;/;

/** 断言失败退出程序并输出错误信息 */
function assert(cond: any, msg?: string) {
    if (!cond) {
        console.log(msg);
        process.exit();
    }
    return cond;
}

/** 字段定义 */
interface IFieldDef {
    lnIdx: number, note: string[], name: string, type: string, struct: string
}

/** 结构定义 */
interface IStructDef {
    note: string[], structName: string, voName: string, fields: IFieldDef[], struct: string
}

/** 通用结构定义 */
interface ICommonDef extends IStructDef {
    fileName: string, structs: IStructDef[]
}

/** 协议定义 */
interface IProtoDef extends ICommonDef {
    /** 协议号 */
    protocol: number,
    /** 协议名 */
    protoName: string
}